using System;
using System.Runtime.InteropServices;

namespace GameFrame.Deterministic
{
    /// <summary>A serializable equivalent of Nullable&lt;FP&gt;.</summary>
    /// 
    ///             \ingroup MathAPI
    [Serializable]
    [StructLayout(LayoutKind.Explicit)]
    public struct NullableFP
    {
        public const int SIZE = 16;
        [FieldOffset(0)]
        public long _hasValue;
        [FieldOffset(8)]
        public FP _value;

        /// <summary>Returns true if this nullable has a value.</summary>
        public bool HasValue => this._hasValue == 1L;

        /// <summary>Returns current value.</summary>
        /// <exception cref="T:System.NullReferenceException">If <see cref="P:Photon.Deterministic.NullableFP.HasValue" /> is false</exception>
        public FP Value
        {
            get
            {
                if (this._hasValue == 0L)
                    throw new NullReferenceException();
                return this._value;
            }
        }

        /// <summary>
        /// If <see cref="P:Photon.Deterministic.NullableFP.HasValue" /> is true, returns <see cref="P:Photon.Deterministic.NullableFP.Value" />. Otherwise returns <paramref name="v" />.
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        public FP ValueOrDefault(FP v) => this._hasValue != 1L ? v : this.Value;

        public static implicit operator NullableFP(FP v) => new NullableFP()
        {
            _value = v,
            _hasValue = 1
        };

        public static unsafe void Serialize(NullableFP* ptr, IDeterministicFrameSerializer serializer)
        {
            if (serializer.Writing)
            {
                serializer.Stream.WriteFP(ptr->_value);
                serializer.Stream.WriteBoolean(ptr->_hasValue == 1L);
            }
            else
            {
                ptr->_value = serializer.Stream.ReadFP();
                ptr->_hasValue = serializer.Stream.ReadBool() ? 1L : 0L;
            }
        }

        public override int GetHashCode() => this._hasValue == 0L ? 0 : this._value.GetHashCode();
    }
}